\chapter{反向执行的设计}

机器人的反向运动是在调试状态下的一个功能。目标是让机器人能够以相同的方式回到上一个目标点。具体的功能设计如下：

\begin{enumerate}
    \item 用户第一次点击“反向”按钮后，程序指针(PP)移动到运动指针(MP)之前的第一条能够反向执行的语句处。
    
    \item 用户再次点击“反向”按钮，系统执行当前PP指向的语句。系统实际执行的是以当前PP指向的语句的目标点为实际目标点，
          当前MP指向的运动历史语句的其余参数组成的一条运动语句。
    
    \item 运动执行完毕之后，控制器会让MP指向运动历史里保存的上一条语句，同时同步给前端。
          前端再次根据这个MP向前查找第一条能反向的语句，设置PP指向它。

    \item 用户再次点击反向按钮，前端执行当前PP指向的语句。系统实际执行的是以当前PP指向的语句的目标点，
          当前MP指向的语句的其余参数组成的一条运动语句。

    \item 用户点击“前向”按钮，系统根据当前MP找到它后面的第一条语句，设置PP指向它。然后将控制器状态置为“前向”。

    \item 用户恢复正常前向调试模式，系统从当前MP开始记录新的运动历史。
\end{enumerate}

\section{实现方式}

反向执行需要语言（前端）和控制器（后端）配合完成。语言前端负责触发包含实际目标点的运动语句的执行。

控制器需要维护一个方向状态和运动历史。

示教器在用户首次点击反向按钮时，将控制器置为“反向”状态。

控制器在反向状态下接收到运动指令后，以接受到的指令的目标点为目标点，同时从运动历史中通过当前MP获取剩
余的参数，完成一次运动，使机器人回到前端触发的运动指令目标点处。然后将MP指向运动历史中的上一条，
并同步设置前端（示教器）里的MP指针。

也就是说在反向执行时，运动历史是不增长的，MP会随着反向运动向前挪动。

前端在运动完成后，根据后端同步回来的MP，查找之前能反向执行的第一条运动语句，设置PP指向它。等待用户的下一步命令。

\section{一些讨论}

\subsection{目标点参数也应该从后端拿}
如果运动指令中的目标点是计算出来的变量，而不是常量，那么仅触发运动指令是无法拿到正确的目标点的。
为了让反向运动的实现更鲁棒，在实际实现中，所有的参数，包括目标点都应该从运动历史中获取。前端
仅仅起到一个触发运动的作用。

\subsection{断电重启怎么办}
这个反向功能是一个调试功能，不是针对断电重启的情况使用的。

\subsection{用户跳转了PP怎么办}
用户跳转了PP之后，确实会以某种方式造成代码和实际运动历史的不一致。但这种情况无法完善处理。

反向功能是为了在正常顺序执行情况下反向运动设计的。它通过前端的程序语句来触发反向运动，以
运动历史中MP指向的语句的参数为运动方式。用户如果不满足这个条件，反向执行有可能失败。
（参考ABB的手册，它对于反向执行的要求限制非常严格）。

\subsection{为什么前端不能把反向运动的所有参数都拿到}

前端只能执行当前PP指向的语句。它执行的是load, pusharg, call这些汇编指令。一方面它没有运动参数的概
念，另一方面它也无法获取到当前执行的语句之外的信息。

前端执行PP指向的语句，向后端触发运动，这对于前端来说就是一次普通的step over操作。

后端收到反向运动触发之后，根据保存的运动历史和运动指针，来完成一次运动，然后让程序执行线程继续执行，
回到前端的调试循环当中。

这样的设计是最符合正常执行逻辑的。

如果我们硬要让前端连续触发2条运动指令，一方面需要前端对PP进行多次跳转。但困难的是，后端如何判别当前
是第一条指令，所以不要让控制流等待，而到第2条指令的时候则要让控制流等待。这个逻辑很难正确地实现。

最困难的是，对于有backward handler的函数，我们每次又只需要执行一条语句了，因为backward handler是作为
整体连续执行的，不能来回来去的调PP。

而使用目前的设计，对于有backward handler的执行逻辑也是一致的：前端触发一个move语句，后端在运动历史里
获取对应的参数。

总结一下就是：我们需要保持前端触发一条运动语句，后端就执行一次运动的执行逻辑。而要求前端获取全部参数
会破坏这个模式，一会儿需要执行2个语句，一会儿又只执行1个语句，还要来回来去跳转PP，后端还得调整控制
流是否等待。

从ABB的文档中也可以获得一些线索。ABB要求backward handler的语句必须严格镜像正向执行的语句。说明
它在反向执行的时候也不是只根据backward handler里的语句来执行的。而是要和之前的正向执行历史匹配。
它的文档中说，如果backward handler的语句没有匹配的话，反向执行会出错。说明它就是在根据运动历史
来执行反向运动的。不是单纯的让前端来驱动反向执行。

最后看一个例子。

\begin{verbatim}
void moveto() {
    move1
    move2
    move3

backward {
        move3
        move2
        move1
    }
}

void main() {
    moveto()
    move4  <-- MP
    next stmt <-- PP
}
\end{verbatim}

现在运动执行完毕move4，PP指向它后面的语句。假设我们要实现一次触发2条运动语句。
在反向执行的时候，PP应该触发move3，以及move4. 获取move3的目标点，以及move4的其他参数。

但是当PP触发完move3之后，当前的调用栈顶层是moveto()。 PP位于moveto()的call frame，它是不能回到main()
的call frame里去触发move4的。

所以这个例子就说明了前端无法提供2条语句的参数。
